//+build !windows

package gitee

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"golang.org/x/sys/unix"
)

func processIsRunning(pid int) error {
	if err := syscall.Kill(pid, 0); err == syscall.ESRCH {
		return err
	}
	return nil
}

func appForceExit(name string, pidfile string, pid int) error {
	err := syscall.Kill(pid, syscall.SIGTERM)
	if err != nil {
		return fmt.Errorf("cannot kill %s[%d]", name, pid)
	}
	for i := 0; i < 50; i++ {
		time.Sleep(100 * time.Millisecond)
		if err := syscall.Kill(pid, 0); err != nil {
			break
		}
	}
	defer os.Remove(pidfile)
	if err := syscall.Kill(pid, 0); err == nil {
		_ = syscall.Kill(pid, syscall.SIGKILL)
		return fmt.Errorf("Stop %s timeout", name)
	}
	return nil
}

// AppExit exit app
func AppExit(name string, pidfile string, force bool) error {
	pid, err := AppIsRunningEx(pidfile)
	if err != nil {
		return fmt.Errorf("%s is not running", name)
	}
	_, err = os.FindProcess(pid)
	if err != nil {
		return fmt.Errorf("%s is not running", name)
	}

	if force {
		return appForceExit(name, pidfile, pid)
	}
	if err := syscall.Kill(pid, syscall.SIGUSR1); err != nil {
		fmt.Fprintf(os.Stderr, "unable kill \x1b[33m%s\x1b[33m[\x1b[33m%d\x1b[0m] error: \x1b[31m%v\x1b[0m\n", name, pid, err)
		return err
	}
	_ = os.Remove(pidfile)
	fmt.Fprintf(os.Stderr, "%s \x1b[32m%d\x1b[0m is stopped\n", name, pid)
	return nil
}

// linux arm64 no dup2 syscall. golang/x/sys/unix use Dup3 impl
/*
func Dup2(oldfd int, newfd int) (err error) {
	return Dup3(oldfd, newfd, 0)
}
golang.org/x/sys/unix/syscall_linux_arm64.go
*/

func appInitializeFD(stderr string) bool {
	var stderrfile string
	if len(stderr) == 0 {
		stderrfile = os.DevNull
	} else {
		stderrfile = filepath.Clean(stderr)
	}
	file, err := os.OpenFile(os.DevNull, os.O_RDWR, 0)
	if err != nil {
		return false
	}
	defer file.Close()
	fd := file.Fd()
	_ = unix.Dup2(int(fd), int(os.Stdin.Fd()))
	_ = unix.Dup2(int(fd), int(os.Stdout.Fd()))
	if stderrfile == os.DevNull {
		_ = unix.Dup2(int(fd), int(os.Stderr.Fd()))
		return true
	}
	dir := filepath.Dir(stderr)
	if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
		_ = os.MkdirAll(dir, 0776)
	}
	filestderr, err := os.OpenFile(stderr, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		return false
	}
	_ = unix.Dup2(int(filestderr.Fd()), int(os.Stderr.Fd()))
	filestderr.Close()
	return true
}

// AppDaemonizedEx todo
func AppDaemonizedEx(name, pidfile, stderr string) error {
	pid, err := AppIsRunningEx(pidfile)
	if err == nil {
		return fmt.Errorf("%s is running, pid=%d", name, pid)
	}
	ek := StrCat(strings.ToUpper(name), "_DAEMONIZED")
	if os.Getenv(ek) != "" {
		return AppImmobilized(pidfile)
	}
	wd, err := os.Getwd()
	if err != nil {
		wd, _ = filepath.Split(os.Args[0])
	}
	appInitializeFD(stderr)
	files := []*os.File{os.Stdin, os.Stdout, os.Stderr}
	exe, _ := os.Executable()
	sysattrs := syscall.SysProcAttr{Setsid: true}
	env := append(os.Environ(), StrCat(ek, "=1"))
	procAttr := &os.ProcAttr{
		Dir:   wd,
		Env:   env,
		Sys:   &sysattrs,
		Files: files,
	}
	_, err = os.StartProcess(exe, os.Args, procAttr)
	if err != nil {
		os.Exit(1)
		return err
	}
	os.Exit(0)
	return nil
}

// AppRestart restart app
func AppRestart(name string, pidfile string, arg ...string) error {
	pid, err := AppIsRunningEx(pidfile)
	if err != nil {
		return fmt.Errorf("%s is not running", name)
	}
	err = syscall.Kill(pid, syscall.SIGUSR1)
	if err != nil {
		return err
	}
	for i := 0; i < 100; i++ {
		if syscall.Kill(pid, 0) != nil {
			break
		}
		time.Sleep(1 * time.Millisecond)
	}
	_ = os.Remove(pidfile)
	exe, err := os.Executable()
	if err != nil {
		return err
	}
	var cmd exec.Cmd
	cmd.Path = exe
	cmd.Args = append([]string{exe}, arg...)
	err = cmd.Run()
	if err != nil {
		return err
	}
	return nil
}

// new app restart
// only send SIGUSR2
func NewAppRestart(name, pidfile string) error {
	pid, err := AppIsRunningEx(pidfile)
	if err != nil {
		return fmt.Errorf("%s is not running", name)
	}
	return syscall.Kill(pid, syscall.SIGUSR2)
}
